/*
 * @Descripttion: 类似 session 的存储
 * @version: 1.0
 * @Author: 自由如风
 * @Date: 2021-05-18 13:08:46
 * @LastEditors: 自由如风
 * @LastEditTime: 2021-07-06 13:25:09
 */

import fs from 'fs'
import path from 'path'
import { echo } from '../tool/echo';
import { defaultOption } from '../tool/option'


/** 仓库里的数据类型 */
interface interfaceStoreData { fresh?: boolean, [key: string]: Object | any, }

/** 
 * 在使用之前需要指定 _path 为
 * 数据仓库，使用openID可以构造一个基于该openID的仓库
 * 每个openID只对应一个仓库
 */
export class Store {
    /** 保存仓库的绝对位置 */
    private static _path: string = defaultOption.store_path;
    public static get path() { return this._path; }
    public static set path(_path: string) {
        if (_path) {
            this._path = _path;
        }
        Store.check();
    }
    /** 判断目录是否存在，不存在则创建 */
    public static check() {
        if (!fs.existsSync(this._path)) {
            fs.mkdirSync(this._path)
            echo(`仓库目录不存在，已创建[${this._path}]`)
        }
    }
    /** 保存单次数据的属性 */
    public data: interfaceStoreData

    /** 构造函数，构造一个唯一的sotre */
    constructor(public key: string) {
        this.data = Store.onRead(key);
    }

    /** 设置一个键值对 */
    public set(key: string, value: any) {
        this.data[key] = value;
        return this;
    }
    /**
     * 合并一个数据，用来代替 set 多次设置
     * @param data 内部采用 ES6 解构语法，所以应该考虑对象浅拷贝的问题
     * @returns 
     */
    public join(data: interfaceStoreData) {
        this.data = { ...this.data, ...data }
        return this;
    }

    /**
     * 获取这个键的值
     * @param {string} key 键名 
     * @param {string} default_value 不存在这个键的时候返回
     */
    public get(key: string, default_value: any = null) {
        return this.data[key] || default_value;
    }

    /** 一次获取所有数据 */
    public getAll() {
        return this.data;
    }

    /**
     * 清除当前所有数据,
     * @returns 
     */
    public clean() {
        this.data = {};
        return this;
    }

    /**
     * 将数据持久化
     * 如果不需要，则无需调用此方法
     */
    public save() {
        Store.onWrite(this.key, this.data);
    }


    /**
     * 数据持久化保存数据,如果你需要自定义，则覆盖这个【属性】
     * @param unique_key 唯一键，用来区分不同的仓库
     * @param data 保存的数据
     */
    public static onWrite = function (unique_key: string, data: interfaceStoreData) {
        Store.check();
        try {
            fs.writeFileSync(path.join(Store.path, unique_key + '.json'), JSON.stringify(data))
        } catch (e) {
            echo(`将数据写入至文件中失败，检查路径是否正确或是否具备读写权限。错误信息：${(e as Error).message}`)
        }
    }

    /**
     * 数据持久化读取数据,如果你需要自定义，则覆盖这个【属性】
     * @param {string} unique_key 唯一键，用来区分不同的仓库
     * @returns 返回可以初始化仓库的对象，应该时只包含一层结构的普通数据对象
     */
    public static onRead = function (unique_key: string): interfaceStoreData {
        Store.check();
        try {
            const tmp_path = path.join(Store.path, unique_key + '.json')
            const attach: interfaceStoreData = { fresh: undefined }

            // 判断文件是否存在
            if (!fs.existsSync(tmp_path)) {
                fs.writeFileSync(tmp_path, '{}'); // 写入空对象
                attach.fresh = true; // 认为这是一个新鲜的仓库
            } else {
                attach.fresh = false; // 认为这不是一个新鲜的仓库
            }

            const data = JSON.parse(fs.readFileSync(tmp_path).toString());
            return { ...data, ...attach };
        } catch (e) {
            echo(`从文件中读取数据失败，检查路径是否正确或是否具备读写权限。错误信息：${(e as Error).message}`)
            return {};
        }
    }
}
